'use strict'

const process = require('node:process');
const {spawn} = require('node:child_process')
const path = require('node:path')
const fs = require('node:fs')
const os = require('node:os')
const crypto = require('node:crypto')
const nps = require('./nps.js')
const cgroup = require('./cgroup.js')

let fsp = fs.promises

let cpuTotal = os.cpus().length

/**
 * 可用CPU用于进程的CPU资源占用控制
 */
let availCPU = 1

if (cpuTotal == 2 || cpuTotal == 3) {
  availCPU = cpuTotal - 0.5
} else if (cpuTotal > 3) {
  availCPU = cpuTotal - 1
}

/**
 * name
 * file
 * command
 * args
 * options
 * restart: always | count | fail | none
 * restartLimit
 * restartDelay default 100
 * user
 * group
 */
/**
 * 我将尽可能把所有功能放在一个文件里，并分块以注释分隔和说明。
 */

let outError = (err) => {
  setTimeout(() => {
    if (typeof err === 'string') {
      console.error(`\x1b[1;35m${err}\x1b[0m`)
    } else {
      console.error(err)
    }
  }, 120)
}

let _stat_index = {
  pgrp    : 4,
  session : 5,
  utime   : 13,
  stime   : 14,
  cutime  : 15,
  cstime  : 16,
  vsize   : 22,
  rss     : 23,
}

/**
 * 多数情况下，默认的页大小都是4K，但是这跟系统以及硬件环境有关。不排除会有特殊情况出现，比如配置启用了hugepagesize。
 * 如何计算页面大小：从开始启动，程序就获取自身的信息，通过获取status和stat文件的信息，计算出页面大小。
 * 主要目的是仍然利用stat获取进程的负载信息，这里有cpu时间片，而status中无法获取。
 * @param {number|string} pid 
 * @param {object} pobj 
 * @returns {object}
 */
async function parse_linux_proc_stat(pid, pobj) {
  if (!pid && pid !== 0) {
    return pobj
  }

  let comm = await fsp.readFile(`/proc/${pid}/comm`, {encoding: 'utf8'})
  let data = await fsp.readFile(`/proc/${pid}/stat`, {encoding: 'utf8'})

  let dlines = data.replace(comm, '$').split(' ').filter(p => p.length > 0)
  
  pobj.utime = parseInt(dlines[_stat_index.utime])
  pobj.stime = parseInt(dlines[_stat_index.stime])
  pobj.cutime = parseInt(dlines[_stat_index.cutime])
  pobj.cstime = parseInt(dlines[_stat_index.cstime])
  pobj.rss = parseInt(dlines[_stat_index.rss])
  pobj.pgrp = parseInt(dlines[_stat_index.pgrp])
  pobj.session = parseInt(dlines[_stat_index.session])

  return pobj
}

async function get_linux_pagesize() {
  let obj = {}
  try {
    await parse_linux_proc_stat(process.pid, obj)
    let p = nps.parse_linux_status(process.pid)

    let pagesize = p.rss / obj.rss + 0.2

    return isNaN(pagesize) ? false : parseInt(pagesize)
  } catch (err) {
    return false
  }
}

/**
   user   (1) Time spent in user mode.
   nice   (2) Time spent in user mode with low priority (nice).
   system (3) Time spent in system mode.
   idle   (4) Time spent in the idle task.
   iowait (since Linux 2.5.41)
       (5) Time waiting for I/O to complete.
   irq (since Linux 2.6.0)
      (6) Time servicing interrupts.
   softirq (since Linux 2.6.0
     (7) Time servicing softirqs.
   steal (since Linux 2.6.11)
      (8) Stolen time, which is the time spent in other 
          operating systems when running in a irtualized  environment
   guest (since Linux 2.6.24)
      (9) Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
   guest_nice (since Linux 2.6.33)
      (10)  Time  spent running a niced guest.
  See: man 5 proc.
 */

async function parse_linux_cpu(options = {all: false}) {
  let data = await fsp.readFile('/proc/stat', {encoding: 'utf8'})
  let dlines = data.split('\n')
  let cone = dlines[0].split(' ').filter(p => p.length)

  let tmpval = 0

  for (let i = cone.length - 1; i > 0; i--) {
    tmpval += parseInt(cone[i])
  }

  let totalCPULoad = {
    total: tmpval,
    idle: parseInt(cone[4])
  }

  if (!options || !options.all) {
    return totalCPULoad
  }

  let cpus = []

  cpus.push(totalCPULoad)

  let dtmp

  for (let i = 0; i < cpuTotal; i++) {
    tmpval = 0
    dtmp = dlines[i+1].split(' ').filter(p => p.length)

    for (let k = dtmp.length - 1; k > 0; k--) {
      tmpval += parseInt(dtmp[k])
    }

    cpus.push({
      total: tmpval,
      idle: parseInt(dtmp[4])
    })

  }

  return cpus
}

/**
 * 计算总的CPU负载
 * @param {object} c1 
 * @param {object} c2 
 */
function caclt_cpu_load(c1, c2) {
  let idle = c2.idle - c1.idle
  let total = c2.total - c1.total

  return (total * 1.0 - (idle * 1.0)) / total
}

function caclt_proc_total_time(p) {
  return p.utime + p.stime + p.cutime + p.cstime
}

/**
 * 计算单个进程的CPU负载
 * @param {object} p1 
 * @param {object} p2 
 * @param {object} c1 
 * @param {object} c2 
 */
function caclt_proc_cpu(p1, p2, c1, c2) {
  let p1_total = caclt_proc_total_time(p1)
  let p2_total = caclt_proc_total_time(p2)
  
  return ((p2_total - p1_total) * 1.0 * cpuTotal) / (c2.total - c1.total)
}

function fmt_percent(n) {
  return (n * 100).toFixed(2)
}

let _linux_meminfo = {}

async function parse_linux_mem() {
  let data = await fsp.readFile('/proc/meminfo', {encoding: 'utf8'})
  let dlines = data.split('\n')

  let t
  let ktmp
  let k

  dlines.forEach((a,ind) => {
      if (!a) return

      t = a.split(' ').filter(p => p.length > 0)

      k = t[0].substring(0, t[0].length - 1)

      ktmp = _linux_meminfo[k]

      if (ktmp) {
        ktmp.value = parseInt(t[1])
        ktmp.unit = t.length > 2 ? t[2].toLowerCase() : ''
      } else {
        _linux_meminfo[k] = {
          value: parseInt(t[1]),
          unit: t.length > 2 ? t[2].toLowerCase() : ''
        }
      }
  })

}

function fmt_mem_value(v, unit='kb') {
  if (unit === 'kb') {
    return ((v * 1.0) / 1024).toFixed(2)
  }

  if (unit === 'mb') {
    return v.toFixed(2)
  }

  if (unit === 'b') {
    return (v * 1.0 / 1024 / 1024).toFixed(2)
  }
}

//判断一个数字在某个2的整数次幂范围，并返回最接近的整数。
function auto_pagesize(n) {
  if (n <= 4) return 4

  let t = 2
  let left_t,right_t

  for (let i = 2; i < 22; i++) {
    left_t = 2**i

    if (n === left_t) return n

    right_t = 2**(i+1)
    
    if (n === right_t) return n

    if (n > left_t && n < right_t) {
      return ((right_t - n) > (n - left_t)) ? left_t : right_t
    }
  }

  return n
}

/**
 * 解析进程的网络收发数据。
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
lo: 49225601  141541    0    0    0     0          0         0 49225601  141541    0    0    0     0       0          0
eth0: 358472071 1977867    0    0    0     0          0         0 918282959 2145431    0    0    0     0       0          0
 * @param {int} pid 
 */

async function parse_linux_pid_net(pid, rtdata, rtindex=[1,2,9,10]) {
  try {
    if (!pid && pid !== 0) return false

    let netfile = `/proc/${pid}/net/dev`
    let netdata = await fsp.readFile(netfile, {encoding: 'utf8'})
    
    let datalines = netdata.split('\n').slice(2)

    let last_recv = 0
    let last_tran = 0

    if (!rtdata) {
      rtdata = {
        recvBytes: 0,
        recvPackets: 0,
        transmitBytes: 0,
        transmitPackets: 0,
        getTime: 0,
        duration: 0,
        recvRate: 0,
        transmitRate: 0,
        devData: {}
      }
    } else {
      last_recv = rtdata.recvBytes
      last_tran = rtdata.transmitBytes
      rtdata.recvBytes = 0
      rtdata.recvPackets = 0
      rtdata.transmitBytes = 0
      rtdata.transmitPackets = 0
    }
    
    let dl, recv,recvpack,tran,tranpack
    let [rind, rpind, tind, tpind] = rtindex

    let tm = Date.now()
    let tmdiff = 0

    for (let d of datalines) {
      dl = d.split(' ').filter(p => p.length > 0)

      if (dl.length < 11) continue

      recv = parseInt(dl[rind]) || 0
      recvpack = parseInt(dl[rpind]) || 0
      tran = parseInt(dl[tind]) || 0
      tranpack = parseInt(dl[tpind]) || 0

      rtdata.recvBytes += recv
      rtdata.recvPackets += recvpack
      rtdata.transmitBytes += tran
      rtdata.transmitPackets += tranpack

      rtdata.devData[dl[0]] = {
        recvBytes: recv,
        recvPackets: recvpack,
        transmitBytes: tran,
        transmitPackets: tranpack,
      }
    }

    rtdata.duration = tm - rtdata.getTime
    rtdata.getTime = tm

    rtdata.recvRate = parseInt((rtdata.recvBytes - last_recv) * 1000 / rtdata.duration)
    rtdata.transmitRate = parseInt((rtdata.transmitBytes - last_tran) * 1000 / rtdata.duration)

    return rtdata
  } catch(err) {
    return false
  }
}

async function parse_linux_socket(obj) {
  let data = await fsp.readFile(`/proc/net/sockstat`, {encoding: 'utf8'})

  let dlines = data.split('\n').filter(p => p.length > 0)

  let tcps = dlines[1].split(' ').filter(p => p.length > 0)

  tcps.length > 2 && (obj.tcp.inuse = parseInt(tcps[2]) || 0)

  let udps = dlines[2].split(' ').filter(p => p.length > 0)

  udps.length > 2 && (obj.udp.inuse = parseInt(udps[2]) || 0)

}

async function parse_linux_socket6(obj) {
  let data = await fsp.readFile(`/proc/net/sockstat6`, {encoding: 'utf8'})

  let dlines = data.split('\n').filter(p => p.length > 0)

  let tcps = dlines[0].split(' ').filter(p => p.length > 0)

  tcps.length > 2 && (obj.tcp.inuse = parseInt(tcps[2]) || 0)

  let udps = dlines[1].split(' ').filter(p => p.length > 0)

  udps.length > 2 && (obj.udp.inuse = parseInt(tcps[2]) || 0)

}

/** -- start cdpc -- */

function _checkAppName(name) {
  if (['__all__'].indexOf(name) >= 0) return false

  //16个字符用于名字，4个字符用于编号，8个字符作为备用空间，绝对足够使用。
  return (/^[a-z1-9_][a-z0-9_-]{0,28}$/i).test(name)
}

function _realTimeout(timeout, stopTimeout) {
  let real_timeout = (isNaN(timeout) || timeout < 5) ? 5000 : timeout

  if (stopTimeout && stopTimeout >= 5 && stopTimeout <= 30000) {
    real_timeout = stopTimeout
  }

  return real_timeout
}

class CDPC {
  constructor(options={}) {
    this.cpus = os.cpus()
  
    this.cpuTotal = this.cpus.length
  
    this.state = {
      PREPARE: 'p',
      EXIT: 'e',
      RUNNING: 'r',
      PAUSE: 's',
      ERROR: '!'
    }

    this.nps = nps.nps
    this.anps = nps.anps
  
    this.stateName = {}
  
    for (let k in this.state) this.stateName[ this.state[k] ] = k
  
    this.command = process.argv[0]
  
    this.args = process.argv.slice(1)
  
    this.maxCount = 365435296162

    //cache monitor timer
    this.monitorTimer = null
    this._procLoadCurPage = 1
    this._totalPage = 0
  
    /**
     * 记录在某些服务运行后才运行的依赖关系。
     * app1 : [
     *    'x', 'y', 'z'
     * ]
     * 表示x y z 服务要在app1运行后才运行。
     * 此时，要对每个依赖的服务记录引用计数，一旦一个所依赖的服务运行，则引用计数 - 1。
     */
    this.relationAfter = {}
  
    this.childs = {}
  
    this.appName = {}
  
    //记录哪些App需要监控。
    this.monitorChilds = []
  
    this.notExit = false
    this.notExitButSpread = false
    this.childDetached = false

    this.beforeStartCallback = false
  
    this.userFile = '/etc/passwd'
    this.groupFile = '/etc/group'
    this.config = ''
    this.debug = false
    this.eventDir = '/tmp/cdpc_watch_wxm'
    this.notWatch = false
    this.showColor = false

    //若设置则表示要把负载信息写入到文件。
    //若设置为--mem，则表示在内存存储，如何使用需要自己实现。
    this.loadInfoFile = ''
  
    //text | json | html | colortext
    this.loadInfoType = 'text'
  
    this.errorHandle = (err, errname = '--ERR--') => {
      if (errname === '--ERR-CONFIG--') {
        this.debug && outError(this.lastErrorInfo)
        return
      }
      this.debug && console.error(errname, err)
    }
  
    if (!options || options.toString() !== '[object Object]') {
      options = {}
    }
  
    //SIGHUP 用于命令程序的配置读取。
    this.signals = [
      'SIGINT', 'SIGTERM', 'SIGABRT', 'SIGQUIT'
    ]
  
    this.sigCount = {
      SIGTERM: 0,
      SIGINT: 0,
      SIGABRT: 0,
      SIGQUIT: 0
    }
  
    //不太优雅的选项处理，不打算抽离出专门的选项处理模块。
    for (let k in options) {
        switch (k) {
            case 'signalHandle':
            case 'onExit':
            case 'errorHandle':
            case 'beforeStartCallback':
                if (typeof options[k] === 'function') this[k] = options[k];
                break

            case 'userFile':
            case 'groupFile':
            case 'config':
                if (typeof options[k] === 'string') {
                  try {
                    fs.accessSync(options[k])
                    this[k] = options[k]
                  } catch (err) {
                    console.error(err)
                  }
                }
                break
  
            case 'eventDir':
              if (typeof options[k] === 'string') this[k] = options[k];
              break
  
            case 'signalNotExit':
            case 'notExit':  
              this.notExit = !!options[k]
              break

            case 'debug':
            case 'notWatch':
            case 'notExitButSpread':
            case 'childDetached':
              this[k] = !!options[k]
              break
            
            case 'loadInfoFile':
              if (typeof options[k] === 'string') this[k] = options[k];
              break
            
            case 'loadInfoType':
              if (['text', 'json'].indexOf(options[k]) >= 0)
                this[k] = options[k];
              break
  
            case 'showColor':
              this[k]= !!options[k]
              break
  
            default:;
  
        }
  
    }//end for
  
    process.on('exit', this.onExit.bind(this))
  
    for (let sig of this.signals) {
      process.on(sig, this.signalHandle.bind(this))
      this.sigCount[sig] = 0
    }
  
    //若没有监听SIGHUP，则默认监听此信号用于重新加载配置文件，此操作会先停止所有服务，并重新启动。
    if (process.listenerCount('SIGHUP') <= 0) {
      process.on('SIGHUP', async sig => {
        await this.reLoadConfig()
      })
    }

    if (process.listenerCount('SIGALRM') <= 0) {
      process.on('SIGALRM', sig => {})
    }
  
    this.lastErrorInfo = ''
  
    this.watchHandleTime = 490
    
    this.watchHandleTable = {}
  
    this.watchEvents = [
      'stop', 'start', 'resume', 'pause', 'restart', 'remove',
      'restartCount', 'resetCount', 'forceRemove', 'safeRemove',
      'disable', 'enable', 'state'
    ]
  
    this.linuxUsers = {}
    this.linuxGroups = {}
  
    this.startWatch()
  
    this.initMonitorData()
  
    this.initCgroup()

    this.processData = this.nps()
    this.processDataTime = Date.now()
  }

  getProcessData() {
    let tm = Date.now()
    if (tm - this.processDataTime > 1000) {
      this.processData = this.nps()
      this.processDataTime = tm
    }

    return this.processData
  }

  initCgroup() {
    if (!process.geteuid || process.platform !== 'linux') return false
  
    if (process.geteuid() !== 0) return false
  
    this.cgroup = new cgroup()
  
    this.removeCgroup = async (name) => {
      let cgrp_path = this.cgroup.findPath(name)
      
      if (!cgrp_path) return false
  
      return new Promise((rv, rj) => {
            this.runChilds({
              autoRemove: true,
              restart: 'count',
              restartLimit: 0,
              monitor: false,
              command: 'rmdir',
              args: [name],
              cwd: this.cgroup.cgdir,
              user: 'root',
              callback: (ch, cm, chs) => {
                ch.on('exit', (code) => {
                  rv(code === 0 ? {ok: true} : {ok: false})
                })
  
                ch.on('error', err => {
                  rj(err)
                })
              }
            })
        }).catch (err => {
          return {ok: false, message: err.message}
        })
    }
  }

  //子进程被创建后会自动放入到父进程所在的cgroup
  async addToCgroup(chk, cname) {
    if (!this.cgroup || process.geteuid() ) return {
      ok: false,
      message: '非root用户不具备操作权限'
    }

    if (typeof chk === 'string') {
      chk = this.find(chk)
    }

    if (!chk || typeof chk !== 'object') return { ok: false, message: '不是合法的子进程'}

    if (!chk.child) return {ok: false, message: '未运行'}

    try {
      return this.cgroup.addPids(cname, [chk.child.pid])
    } catch (err) {
      return {ok: false, message: err.message}
    }

    return {ok: true}
  }

  initMonitorData() {
    this.lastCPULoad = null

    this.loadinfo = {
      cpu: 0,
      cpun: 0,
      cpus: [],
      cpuns: [],
      socket: {tcp:{}, udp: {}},
      socket6: {tcp:{}, udp: {}},
      mem: _linux_meminfo
    }

    this._procPagesize = 5
    this._curProcList = []
    this._procLoopCount = 0
    this._memPagesize = 4
    this._lastCPULoad = null
    this._curCPULoad = null
    this.__child_number = 0

    //采用步进式策略，定时器会非常快速的执行，但是不会每次都获取监控信息。
    this.stepSlice = 5
    this.maxStep = 100
    this.stepCount = 100
    this.dynamicMaxStep = 105
    this.useDynamicStep = true
    //动态步进的每次前进间隔
    this.dynamicStep = 1
    
    if (process.platform !== 'linux') return false

    get_linux_pagesize().then(psize => {
      if (!psize || psize < 4) return false;

      //页面文件大小只能是2的整数次幂：4 8 16 ... Linux支持huagePagesize到2M，也可能极端情况会有1G。
      this._memPagesize = auto_pagesize(psize)
    })
  }

  setStepSlice(tslice) {
    if (typeof tslice !== 'number' || tslice < 1 || tslice > 100) return false

    this.stepSlice = tslice

    return this.stepSlice
  }

  setMaxStep(max_step=100, dyn_step=0) {
    if (isNaN(max_step)) return false

    if (isNaN(dyn_step)) dyn_step = 0

    if (max_step >= 1 && max_step < 500) {
      this.maxStep = max_step
    }

    if (dyn_step > 0) {
      this.dynamicMaxStep = dyn_step
    }
  }

  setDynamicStep(max_step=0) {
    if (isNaN(max_step)) return false

    this.dynamicMaxStep = max_step

    return this.dynamicMaxStep
  }

  killChilds(sig, quiet=false) {
    let ch;
    let count = 0;
    for (let k in this.childs) {
      ch = this.childs[k];
      if (ch.state !== this.state.EXIT && ch.child && ch.child.kill) {
        count++;
        try {
          ch.child.kill(sig);
        } catch (err) {
          !quiet && this.errorHandle(err, '--ERR-CHILD-KILL--');
        }
      }
    }

    return count;
  }
  
  async signalHandle(sig) {
    if (this.notExit) {
      return 
    }
  
    //为了保证一些终止信号在退出时不会重复发送。
    ;(this.sigCount[sig] !== undefined) && (this.sigCount[sig] += 1);
  
    if (this.signals.indexOf(sig) >= 0) {
      //此处需要考虑是否替换为killAllChilds，目前运行良好，暂时可以不更新。
      let count = this.killChilds(sig)
      
      //如果只是扩散但是不退出，则不必继续检测，直接返回。
      if (this.notExitButSpread) {
        return
      }

      //此处不能立即退出，要给子进程留出时间进行清理工作。
      for (let i = 0; i < 50; i++) {
        if (count <= 0) {
          process.exit(0)
        }
        
        await new Promise((rv, rj) => {
          setTimeout(() => { rv() }, 10)
        })

        this.debug && i > 45 && console.log('存在没有终止的进程···')
        count = this.killChilds(sig)
      }

      if (count > 0) {
        this.debug && console.log('使用SIGKILL终止进程')
        this.killChilds('SIGKILL', true)
      }

      process.exit(0)
    }
  
  }
  
  /**
   * 当程序直接收到信号退出时，并不会执行此函数，
   * 所以一个进程是因为信号异常终止，其子进程会成为守护进程。
   * */
  onExit(code) {
    try {
      if (code === 0) {
        for (let k in this.sigCount) {
          if (this.sigCount[k] > 0) {
            this.sigCount[k] = 0
            return
          }
        }

        this.killChilds('SIGTERM')
        //process.kill(process.pid, 'SIGTERM')
      } else {
        this.killChilds('SIGKILL')
      }
    } catch (err) {
      this.errorHandle(err, '--ERR-EXIT--')
    }
  }
  
  strong(unexp = null, unrej = null) {
    if (!unexp || typeof unexp !== 'function') {
      unexp = (err, orgi) => {
        this.errorHandle(err, `--ERR-${orgi}--`)
      }
    }
    process.on('uncaughtException', unexp)
  
    if (!unrej || typeof unrej !== 'function') {
      unrej = (reason, promise) => {
        this.errorHandle(reason, '--ERR-PROMISE--')
      }
    }
    process.on('unhandledRejection', unrej)
  
  }
  
  tnps(callback, timeout=1000) {
    return setInterval(() => {
      ;(typeof callback === 'function') && this.anps().then(data => {callback(data)})
    }, timeout)
  }
  
  /**
   * 只会kill掉所有子进程
   */
  killAllChilds(pid, sig='SIGTERM') {
    if (process.platform !== 'linux') {
      return
    }
    
    try {
      let pst = nps.nps()
  
      pst.getAllChilds(pid, true).forEach(p => {
        process.kill(p, sig)
      })
    } catch (err) {
      this.errorHandle(err, '--ERR-KILL-ALL-CHILDS--')
    }
  
  }
  
  initEventsDir() {
    this.watchEvents.forEach(d => {
      let dfile = `${this.eventDir}/${d}`
      try {
        fs.accessSync(dfile)
        let fst = fs.statSync(dfile)
        if (!fst.isDirectory()) {
          fs.unlinkSync(dfile)
          fs.mkdirSync(dfile, {mode: 0o755})
        }
      } catch (err) {
        fs.mkdirSync(dfile, {mode: 0o755})
      }
    })
  }
  
  async startWatch() {
    if (this.notWatch) return;
  
    let evtst = true
  
    try {
      fs.accessSync(this.eventDir)
    } catch (err) {
      evtst = false
    }
  
    if (!evtst) {
      try {
        fs.mkdirSync(this.eventDir)
      } catch (err) {
        this.errorHandle(err, '--ERR-MKDIR-EVENT--')
        return;
      }
    }
  
    this.initEventsDir()
  
    this.watchEvents.filter(x => {
      if (['state', 'childs'].indexOf(x) < 0) return x
    }).forEach(d => {
      let evt_dir = `${this.eventDir}/${d}`
      let event_name = d
  
      fs.watch(evt_dir, (evt, fname) => {
        if (evt === 'rename') {
          try {
            fs.accessSync(`${evt_dir}/${fname}`)
          } catch (err) {
            return false
          }
        }
  
        if (!this.has(fname) && fname !== '__all__') {
          fs.unlink(`${evt_dir}/${fname}`, err => {err && this.errorHandle(err, '--ERR-REMOVE-NOT-HAS--')})
          return false
        }
  
        let ek = event_name + '_' + fname
        let tm = Date.now()
  
        if (!this.watchHandleTable[ek]) {
          this.watchHandleTable[ek] = {time: tm}
        } else {
          if (tm < (this.watchHandleTable[ek].time + this.watchHandleTime + 10) ) {
            return false
          }
        }
        
        this.watchHandleTable[ek].time = tm
  
        let applist = []
        if (fname !== '__all__') {
          applist.push(fname)
        } else {
          for (let k in this.appName) {
            applist.push(k)
          }
        }
  
        for (let a of applist) {
          this[event_name](a)
        }
  
      })
    })
  
    /**
     * 由于watch在测试发现，会连续触发两次，并且如果是rename事件不必处理，只需要对change事件处理。
     * */
    fs.watch(this.eventDir, (evt, fname) => {
        if (['load', 'reload'].indexOf(fname) < 0) return false
  
        let tm = Date.now()
  
        if (!this.watchHandleTable[fname]) {
          this.watchHandleTable[fname] = {time: tm}
        } else {
          if (tm < (this.watchHandleTable[fname].time + this.watchHandleTime + 10) ) {
            return false
          }
        }
  
        this.watchHandleTable[fname].time = tm
        
        if (fname === 'load') {
          try {
            let data = fs.readFileSync(`${this.eventDir}/${fname}`, {encoding: 'utf8'});
            data = data.trim()
            this.loadConfig(data)
          } catch (err) {
            this.errorHandle(err, '--ERR-LOAD--')
          }
          
        } else if (fname === 'reload') {
          this.reLoadConfig()
        }
    })
  
  }
  
  /**
   * reload事件触发不会导致程序重启，这要求在重新设定配置的过程中，必须要识别正在运行的程序
   * @param {string} filename 
   * @returns 
   */
  async reLoadConfig(filename = '') {
      if (!filename && !this.config) return false
  
      try {
        await fsp.access(filename || this.config, fs.constants.F_OK | fs.constants.R_OK)
      } catch (err) {
        this.errorHandle(err, '--ERR-RELOAD-CONFIG--')
        return false
      }
  
      return this.loadConfig(filename, true)
  }
  
  getUserId(uname) {
    if (typeof uname === 'string') {
      uname = [uname]
    } else if (!Array.isArray(uname)) {
      throw new Error('指定的用户必须是字符串或字符串数组。')
    }
  
    for (let name of uname) {
      if (this.linuxUsers[name]) return this.linuxUsers[name]
    }
  
    try {
      let data = fs.readFileSync(this.userFile, {encoding: 'utf8'})
      let dlines = data.split('\n')
                      .filter(p => p.length > 0)
                      .map(a => {
                        return a.split(':')
                      });
  
      for (let name of uname) {
          for (let d of dlines) {
            if (d[0] === name) {
              this.linuxUsers[name] = {
                uid: parseInt(d[2]),
                gid: parseInt(d[3])
              }
  
              return this.linuxUsers[name]
            }
          }
      }
    } catch (err) {
      this.errorHandle(err, '--ERR-USER-ID--')
      return null
    }
  
    return null
  }
  
  getGroupId(grp) {
    if (typeof grp === 'string') {
      grp = [grp]
    } else if (!Array.isArray(grp)) {
      throw new Error('指定的用户组必须是字符串或字符串数组。')
    }
  
    for (let g of grp) {
      if (this.linuxGroups[g]) return this.linuxGroups[g]
    }
  
    try {
      let data = fs.readFileSync(this.groupFile, {encoding: 'utf8'})
      let dlines = data.split('\n')
                      .filter(p => p.length > 0)
                      .map(a => {
                        return a.split(':')
                      });
  
      for (let g of grp) {
          for (let d of dlines) {
            if (d[0] === g) {
              this.linuxGroups[g] = {
                gid: parseInt(d[2])
              }
  
              return this.linuxGroups[g]
            }
          }
      }
  
    } catch (err) {
      this.errorHandle(err, '--ERR-GROUP-ID--')
      return null
    }
  
    return null
  }
  
  readConfig(filename = '', options = {}) {
    if (typeof filename === 'object') {
      options = filename
      filename = ''
    }
  
    let real_file = filename || this.config
  
    if (!real_file) return {
      ok: false,
      errmsg: 'filename is null'
    }
  
    let fst
  
    try {
      fs.accessSync(real_file)
      fst = fs.statSync(real_file)
    } catch (err) {
      return {
        ok: false,
        errmsg: 'file is not exists'
      }
    }
  
    let flist = []
  
    if (fst.isFile()) {
      flist.push(real_file)
    }
    else if (fst.isDirectory()) {
      let files = fs.readdirSync(real_file, {withFileTypes: true})
      let t
      for (let f of files) {
        if (!f.isFile()) continue
        
        t = f.name
  
        if (options && options.ignore && (options.ignore instanceof Array)) {
          if (options.ignore.indexOf(t) >= 0) continue
        }
  
        if (t.substring(t.length - 5) === '.json' || t.substring(t.length - 3) === '.js')
        {
          flist.push(`${real_file}/${t}`)
        }
      }
    }
  
    let cfglist = []
    let data
    let fpath
  
    for (let f of flist) {
      try {
        fpath = path.resolve(f)
  
        //清理模块缓存，让require重新读取配置文件。
        delete require.cache[fpath]
  
        data = require(f)
  
        if ( !Array.isArray(data) ) {
          data.configPath = fpath
          cfglist.push(data)
        } else {
          data.forEach(d => {
            d.configPath = fpath
          })
  
          cfglist = cfglist.concat(data)
        }
  
      } catch (err) {
        this.errorHandle(err, '--ERR-READ-CONFIG--')
      }
    }
  
    return {
      ok: true,
      data: cfglist
    }
  }
  
  loadConfig(filename='', reload=false) {
    if (typeof filename === 'boolean') {
      reload = filename
      filename = ''
    }
  
    let r = this.readConfig(filename)
  
    if (!r.ok) {
      return r
    }

    /**
     * 重新加载模式，会遇到一些问题：
     * - 应用的name改了，或者是文件路径有所更改。
     * - 实际的文件其实已经不存在了，其实就是这个服务的配置文件已经不存在了。
     * 所有这些情况，都没有什么更好的办法，直接全部重启服务即可。
     */
    try {
      this.runChilds(r.data, reload)
    } catch (err) {
      this.errorHandle(err, '--ERR-LOAD-CONFIG--')
  
      return {
        ok: false,
        errmsg: err.message
      }
    }
  
    return {
      ok: true,
      errmsg: 'ok'
    }
  }

  run(config, reload=false) {
    return this.runChilds(config, reload)
  }
  
  runChilds(config, reload=false) {
    if ( !Array.isArray(config) ) {
      config = [ config ]
    }
  
    for (let cfg of config) {
      if ( this.checkConfig(cfg, reload) ) {
        this.tryMakeChild(cfg, reload)
      } else {
        this.errorHandle(this.lastErrorInfo, '--ERR-CONFIG--')
      }
    }
  }
  
  /**
   * 注意：如果依赖的命令不存在会导致问题，比如Node.js版本变化，旧的版本已经被删除了。
   * 检测配置文件参数。
   * @param {object} cfg 
   */
  checkConfig(cfg, reload=false) {
    if (!cfg.args || !Array.isArray(cfg.args)) cfg.args = []
  
    if (!cfg.options) cfg.options = {}
  
    //这表示扩展，因为cfg.options.env默认是process.env
    if (cfg.env && typeof cfg.env === 'object') {
      if (!cfg.options.env || typeof cfg.options.env !== 'object') {
        cfg.options.env = {
          ...process.env
        }
      }
  
      for (let k in cfg.env) {
        cfg.options.env[k] = cfg.env[k]
      }
    }

    if (this.childDetached) {
      cfg.options.detached = true
    }

    if (!cfg.step || typeof cfg.step !== 'number' || cfg.step < 0) cfg.step = 0
  
    if (!cfg.limit || typeof cfg.limit !== 'object') {
      cfg.limit = null
    }
  
    this.fmtLimit(cfg)
  
    if (!cfg.after || (!Array.isArray(cfg.after) && typeof cfg.after !== 'string' ) ) {
      cfg.after = null
    } else if (typeof cfg.after === 'string') {
      cfg.after = [ cfg.after ]
    }
  
    if (!cfg.name) {
      cfg.name = ''
    } else {
      if (cfg.name.length > 28) {
        cfg.name = cfg.name.substring(0, 28)
      }

      if (!_checkAppName(cfg.name)) {
        this.lastErrorInfo = 
          `命名不合法：${cfg.name}，支持字母数字下划线减号，并且以字母或数字开头，长度不超过28。`
  
        return false
      }
    }
  
    if (cfg.detail && cfg.detail.length > 50) {
      cfg.detail = cfg.detail.substring(0, 50)
    }
  
    if (this.appName[cfg.name]) {
      if (!reload) {
        this.lastErrorInfo = `${cfg.name}：应用名称冲突。(${cfg.name} conflict.)`
        return false
      }
    }
  
    if (cfg.file) {
      try {
        fs.accessSync(cfg.file)
      } catch (err) {
        this.debug && outError(err)
        return false
      }
  
      if (!cfg.command) {
        let extname = cfg.file.substring(cfg.file.length - 3)
        let extname2 = cfg.file.substring(cfg.file.length - 4)
        if (extname === '.js' || extname2 === '.cjs' || extname2 === '.mjs') {
          cfg.command = 'node'
          cfg.commandList = [this.command]
        } else if (extname === '.sh') {
          cfg.command = 'bash'
          cfg.commandList = [ 'sh' ]
        } else if (extname === '.py') {
          cfg.command = 'python'
        } else {
          this.lastErrorInfo = `${cfg.name} ${cfg.file}没有指定运行脚本的命令。`
          return false
        }
      }
  
      if (!cfg.command) {
        this.lastErrorInfo = `${cfg.name} 未指定命令`
        return false
      }
  
      let cwd = path.resolve( path.dirname(cfg.file) )
      let cfile = path.basename(cfg.file)
  
      if (!cfg.options.cwd) {
        cfg.options.cwd = cwd
      }
      
      cfg.realfile = `${cwd}/${cfile}`
      
      if (cfg.args.indexOf(cfg.realfile) < 0)
          cfg.args.unshift(cfg.realfile);
    }

    if (!cfg.onlyArgs || !Array.isArray(cfg.onlyArgs)) {
      cfg.onlyArgs = [...cfg.args]
    }
  
    if (cfg.restartDelay === undefined || typeof cfg.restartDelay !== 'number') {
      cfg.restartDelay = 1000
    }
  
    if (cfg.restartLimit !== undefined) {
      if (typeof cfg.restartLimit !== 'number') {
        cfg.restartLimit = 1
      } else if (cfg.restartLimit > this.maxCount) {
        cfg.restartLimit = this.maxCount
      }
    }
  
    if (cfg.restart === undefined || ['count', 'fail', 'always', 'none', 'fail-count'].indexOf(cfg.restart) < 0) {
      cfg.restart = 'always'
    }
  
    if (cfg.stopTimeout === undefined || typeof cfg.stopTimeout !== 'number' || cfg.stopTimeout < 0)
        cfg.stopTimeout = 0;

    if (cfg.onceMode) {
      cfg.autoRemove = true
      cfg.restart = 'count'
      cfg.restartLimit = 0
    }
  
    if (process.platform !== 'win32') {
      if (cfg.user) {
        let ug = this.getUserId(cfg.user)
        if (ug) {
          cfg.options.uid = ug.uid
          cfg.options.gid = ug.gid
        }
      }
  
      if (cfg.group) {
        let g = this.getGroupId(cfg.group)
        if (g) {
          cfg.options.gid = g.gid
        }
      }
  
      if (process.geteuid() === 0) return true
  
      if (cfg.options.uid !== undefined || cfg.options.gid !== undefined) {
        this.lastErrorInfo = 
            `必须以root用户运行才可以改变子进程的uid和gid。\n\t${JSON.stringify(cfg.options)}`
  
        return false
      }
  
    }
  
    return true
  }

  fmtLimit(cfg) {
    if (cfg.limit) {
      //最大内存(KB)、最大内存的基础值(KB)、最长运行时间(ms)、频率(f/s)、一天最大允许的运行次数。
      ;[
        'maxrss', 'rssOffset', 'maxtime', 'frequency', 'maxdaylimit', 'maxRestart'
      ].forEach(x => {
        if (cfg.limit[x] === undefined || typeof cfg.limit[x] !== 'number') {
          cfg.limit[x] = 0
        }
  
        cfg.limit.rssRestartCount = 0
      })
    }
  }

  /**
   * 
   * @param {object} chk
   * 根据配置生成名称，这样的名称在同样的配置上是唯一的。
   */
  makeName(chk) {
    let h = crypto.createHash('sm3')
    let args = Array.isArray(chk.args) ? [...chk.args] : []

    args.sort((a,b) => {return a > b ? 1 : -1})
  
    let data = `${chk.command}${args.join('::')}`
    h.update(data)
    let hname = h.digest('hex')
    let final_name = hname.substring(0, 18)

    if (this.appName[final_name]) {
      return this.serialName(final_name + '_')
    }

    return final_name
  }
  
  serialName(prefix='child_') {
    if (this.__child_number < 10000) {
      this.__child_number++
    } else {
      this.__child_number = parseInt(Math.random() * 10000) + 10001
    }
  
    return `${prefix}${this.__child_number}`
  }
  
  //查找依赖的应用，并更新afterCount
  setAfterCount(chd, op) {
    let name = chd.name;
  
    let tapp;
  
    if (!this.relationAfter[name]) return false;
  
    let rk = this.relationAfter[name];
  
    for (let a of rk) {
      tapp = this.find(a);
      if (!tapp) continue;
  
      if (op === this.state.EXIT) {
          // 修改依赖引用计数，若是此时应用已经运行则无影响，
          // 但是若应用退出，此时如果之前依赖的服务已经退出，则必须要等到依赖的服务运行后才会继续重启。
          tapp.afterCount += 1;
      } else if (op === this.state.RUNNING) {
          tapp.afterCount -= 1;
          if (tapp.afterCount <= 0) { 
            //因为tapp是在外层，导致在异步情况下，最终会都去运行最后一个应用。
            let tmp_app = tapp;
            queueMicrotask(() => {
              this.startChild(tmp_app);
            });
          }
      }
    }
  
    return true;
  }
  
  appEventFile(evt, name) {
    ;(typeof name === 'object') && (name = name.name);
  
    return `${this.eventDir}/${evt}/${name}`
  }
  
  appStateFile(chk) {
    return this.eventDir + '/state/' + chk.name
  }
  
  async writeChildState(chk) {
    if (this.notWatch) return false
    let cname = this.appStateFile(chk)
    let str_state = ''
  
    switch (chk.state) {
      case this.state.ERROR:
        str_state = 'error'
        break
  
      case this.state.PREPARE:
        str_state = 'prepare'
        break
  
      case this.state.RUNNING:
        str_state = 'running'
        break
  
      case this.state.EXIT:
        str_state = 'exit'
        break
  
      case this.state.PAUSE:
        str_state = 'pause'
        break
  
    }
    
    if (chk.disabled) {
      str_state += '(disabled)'
    }
    
    str_state = `${str_state} ${chk.child ? (chk.child.pid||'0') : '0'} ${(new Date()).toLocaleString().replaceAll('/', '-')}`
  
    try {
      await fsp.writeFile(cname, str_state, {encoding: 'utf8'})
    } catch(err) {
      return false
    }
  
    return true
  }
  
  /**
   * 更新子进程的command，这个操作目的在于如果旧的命令被删除，则需要自动更新。
   * */
  setChildCommand(name, command) {
    let ap = this.find(name)
    if (!ap) return false
    let old_ck = `${ap.command}\x00${ap.args.join('\x00')}`
    let ck = `${command}\x00${ap.args.join('\x00')}`
    ap.command = command

    delete this.childs[old_ck]
    this.childs[ck] = ap
    this.appName[name] = ck
  
    return ap
  }

  /**
   * 
   * @param {object} cfg 
   * @param {boolean} reload 
   * @returns {object}
   */
  tryMakeChild(cfg, reload=false) {
    if (!cfg.name) cfg.name = this.makeName(cfg)
    let ck = `${cfg.command}\x00${cfg.args.join('\x00')}`
    let child = null

    if (this.childs[ck]) {
      if (reload) {
        //重新加载，则删除旧的应用，重新创建新的应用。
        child = this.childs[ck].child
        delete this.childs[ck]
      } else if (this.childs[ck].state !== this.state.EXIT) {
        return this.childs[ck]
      }
    }

    if (cfg.monitor === undefined) cfg.monitor = true

    try {
      this.beforeStartCallback && this.beforeStartCallback(cfg)
    } catch (err) {}
    //避免开发过程重置，需要再次进行格式化
    this.fmtLimit(cfg)

    if (!this.childs[ck]) {
      this.childs[ck] = {
        name: cfg.name,
        detail: cfg.detail || '',
        command: cfg.command,
        //用于备份运行的命令
        commandList: cfg.commandList && Array.isArray(cfg.commandList) ? cfg.commandList : [],
        commandIndex: 0,
        cmdline: ck,
        args: cfg.args,
        env: cfg.env,
        options: cfg.options,
        restartCache: cfg.restart,
        restart: cfg.restart,
        restartDelay: cfg.restartDelay,
        after: cfg.after || null,
        afterCount: 0,
        code: null,
        signal: null,
        lockForStart: false,
        lockStartTime: 0,
        cgroup: cfg.cgroup || '',
        //退出或重启的原因，一个字符串描述: event|op|detail
        cause: '',
        restartLimit: cfg.restartLimit,
        state: this.state.PREPARE,
        restartCount: 0,
        //是否正在重启的标志
        restarting: false,
        //是否自动删除，只有在restart为count模式才起作用。
        autoRemove: !!cfg.autoRemove,
        child: child,
        get pid() {
          return this.child ? this.child.pid : null
        },
        callback: cfg.callback || null,
        onError: cfg.onError || null,
        monitor: !!cfg.monitor,
        lockReload: !!cfg.lockReload,
        stopTimer: null,
        stopTimeout: cfg.stopTimeout,
        configPath: cfg.configPath || '',
        disabled: !!cfg.disabled,
        onceMode: !!cfg.onceMode,
        //only为true表示只能有一个服务运行，比如数据库服务。
        only: !!cfg.only,
        onlyArgs: cfg.onlyArgs || cfg.args,
        //是否强制模式，此模式下，会对检测已经运行的服务进行kill处理。
        force: !!cfg.force,
        //资源限制，最开始针对云函数服务加入。
        limit: cfg.limit || {maxrss: 0, rssOffset: 0, maxtime: 0, frequency: 0, maxdaylimit: 0, rssRestartCount: 0},
        //运行时状态，主要还是针对云函数服务，用于识别是不是正在运行云函数，也可以作为其他使用，这是可以自定义的。
        runstate: cfg.runstate || {},
        monitorNetData: !!cfg.monitorNetData,
        loadinfo: {
          last: {},
          cur: {},
          cpu: 0,
          mem: 0,
          net: null
        }
      }

      //这种就是新的配置参数变化导致ck不一致，被标识为两个应用，但是名称一样。
      if (this.appName[cfg.name] && ck !== this.appName[cfg.name]) {
        let old_ck = this.appName[cfg.name]
        let old_chk = this.childs[old_ck]

        this.childs[ck].child = old_chk.child
        delete this.childs[old_ck]

        let ind = this.monitorChilds.indexOf(old_ck)
        if (ind >= 0) {
          this.monitorChilds.splice(ind, 1)
        }
      }
      
      cfg.name && (this.appName[cfg.name] = ck);
  
      this.childs[ck].monitor
          && this.monitorChilds.indexOf(ck) < 0 
                && this.monitorChilds.push(ck);
  
      this.childs[ck].child && (this.childs[ck].state = this.state.RUNNING)
    } else {
      this.childs[ck].state = this.state.PREPARE
    }

    let chd = this.childs[ck]

    //如果child正在运行，则更改状态
    if (chd.child && chd.child.pid && chd.child.exitCode === null && chd.state === this.state.EXIT) {
      chd.state = this.state.RUNNING
    }

    if (chd.state === this.state.RUNNING || chd.state === this.state.PAUSE) {
      return chd
    }
  
    if (!chd.after)
      return this.startChild(this.childs[ck])
  
    chd.afterCount = chd.after.length
  
    //记录关系
    for (let a of chd.after) {
      if (this.relationAfter[a]) {
        ;(this.relationAfter[a].indexOf(chd.name) < 0) && this.relationAfter[a].push(chd.name)
      } else this.relationAfter[a] = [ chd.name ]
    }
  
    let tapp = null
  
    for (let a of chd.after) {
      tapp = this.find(a)
  
      if (!tapp) {
        this.errorHandle(`${a} 不存在的应用导致依赖此应用的服务无法运行`, '--ERR-AFTER-NOT-FOUND--')
        continue
      }
  
      if (tapp && this.state.RUNNING === tapp.state) {
        chd.afterCount -= 1
      }
    }
    
    if (chd.afterCount <= 0) return this.startChild(chd)
  
    return chd
  }
  
  startChild(chk) {
    let tm = Date.now()

    if (chk.lockForStart && tm < (100 + chk.lockStartTime)) return chk

    //目的在于避免重复启动
    chk.lockForStart = true
    chk.lockStartTime = tm

    chk.state = this.state.PREPARE
    this.writeChildState(chk)
  
    if (chk.disabled) {
      chk.lockForStart = false
      chk.state = this.state.EXIT
      this.writeChildState(chk)
      return chk
    }

    if (chk.child && chk.child.pid) {
      chk.state = this.state.RUNNING
      chk.lockForStart = false

      return chk
    }

    //如果是only模式，则需要检测是否已经运行了。
    /**
     * 如果已经存在服务，则当前检测会看到已经存在，
     * 但是因为不知道服务是由systemd管理的还是其他管理的服务，
     * 也可能是因为重启cdpcd导致的一些进程并不随着服务的退出而退出。
     * 默认的模式下，不做处理，直接返回，这样的默认状态就是没有运行的，相关操作：
     * - 重启、启动、停止操作都不会有实质影响，因为记录状态总是EXIT。
     * */
    if (chk.only) {
      this.getProcessData()
      try {
        let nametable = {}
        nametable[chk.command] = true
        if (chk.command.indexOf('/') >= 0) {
          let ns = chk.command.split('/').filter(p => p.length > 0)
          nametable[ns[ns.length-1]] = true
        }

        chk.commandList.forEach(x => {
          !nametable[x] && (nametable[x] = true)
        })

        let cmds = this.processData.getChildsByCMDArgs(nametable, chk.onlyArgs)
        if (cmds.length > 0) {
          if (chk.force) {
            process.kill(cmds[0].pid, 'SIGKILL')
          } else {
            chk.cause = `RUNNING|CMDLINE-CHECK|服务已经运行，并且不属于cdpc管理。`
            this.debug && console.error(chk.cause)
            return chk
          }
        }
      } catch (err) {
        this.errorHandle(err, '--ERR-CMDLINE-CHECK--')
        chk.cause = `${err.code||'CMDLINE'}|CMDLINE-CHECK|服务已经运行，并且不属于cdpc管理，无法强制退出。`
        return chk
      }
    }

    chk.cause = ''
  
    let ch = spawn(chk.command, chk.args, chk.options)
  
    chk.child = ch
    
    ch.on('spawn', (stdout, stderr) => {
      let cur = this.appName[chk.name] ? (this.childs[ this.appName[chk.name] ] || chk) : chk
      cur.lockForStart = false
      cur.restarting = false
      cur.state = this.state.RUNNING

      cur.cgroup && this.addToCgroup(cur, cur.cgroup)
  
      this.setAfterCount(cur, this.state.RUNNING)
      this.writeChildState(cur)
    })
  
    let self = this
  
    ch.on('exit', (code, sig) => {
        let cur = this.appName[chk.name] ? (this.childs[ this.appName[chk.name] ] || chk) : chk
      
        cur.lockForStart = false
        cur.child = null
        cur.code = code
        cur.signal = sig
        cur.state = this.state.EXIT
  
        this.setAfterCount(cur, this.state.EXIT)
        cur.restart !== 'remove' && this.writeChildState(cur)

        //在触发exit事件的时候，后续添加的exit事件函数仍然会被执行，只有此阶段结束，才会在事件循环的下一个环节执行此微任务。
        queueMicrotask(() => {
          try {
            ch && ch.removeAllListeners()
            ch = null
          } catch (err){}
        })

        if (cur.restart === 'none' || cur.restart === 'remove') return
  
        //若收到信号退出，进程不做监听处理，code为null
        if ((cur.restart === 'fail' || cur.restart === 'fail-count') && cur.code === 0) {
          cur.autoRemove && self.remove(cur.name)
          return
        }
  
        if (cur.restart === 'count' || cur.restart === 'fail-count') {
          if (cur.restartCount >= cur.restartLimit) {
            cur.autoRemove && self.remove(cur.name)
            return
          }
        }
  
        (cur.restartCount < this.maxCount) && (cur.restartCount += 1)
  
        if (cur.restartDelay <= 0) {
            this.startChild(cur)
        } else {
            setTimeout(() => {
              this.startChild(cur)
            }, cur.restartDelay)
        }
  
    })
  
    //一个错误事件不一定会导致触发exit事件，若通过callback自定义error事件处理需要注意。
    ch.on('error', err => {
      let cur = this.appName[chk.name] ? (this.childs[ this.appName[chk.name] ] || chk) : chk

      cur.lockForStart = false
      cur.state = this.state.ERROR
      this.writeChildState(cur)

      cur.cause = `${err.code||'ERROR'}|ERROR|${err.message}`
      if (err.code === 'ENOENT') {
        //命令没有发现，此时检查是否有备用命令
        if (cur.commandList && cur.commandList.length > 0) {
          let backup_command = cur.commandList[cur.commandIndex % cur.commandList.length]
          cur.commandIndex++
          //已经重试过2次，不再重新尝试运行
          if (cur.commandIndex >= cur.commandList.length * 2) {
            return this.errorHandle(err, '--ERR-CHILD--')
          }
  
          if (backup_command) {
            this.setChildCommand(cur.name, backup_command)
            return this.startChild(cur)
          }
        }
      }

      this.errorHandle(err, '--ERR-CHILD--')  
    })
  
    if (chk.onError && typeof chk.onError === 'function') {
      ch.on('error', chk.onError)
    }
  
    if (chk.callback && typeof chk.callback === 'function') {
      chk.callback(ch, this, chk)
    }
  
    return chk
  }

  matchCommandName(command, name, args) {
    if (!name || !command) return false
    if (command === name) return true
    let ind = command.lastIndexOf(name)
    if (command.substring(ind) === name) return true

    return false
  }

  /**
   * 
   * @param {string} cmd 
   * @param {function|null} callback 
   * @returns 
   */
  findByCommand(cmd, callback=null) {
    let cb = typeof callback === 'function' ? callback : this.matchCommandName

    let chs = []
    for (let k in this.childs) {
      if (cb(this.childs[k].command, cmd, this.childs[k].args)) {
        chs.push(this.childs[k].name)
      }
    }

    return chs
  }
  
  find(name) {
    if (name && typeof name === 'object') return name

    let ck = this.appName[name]
    
    if (!ck) return null
  
    return this.childs[ck] || null
  }
  
  disable(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    chk.disabled = true

    this.writeChildState(chk)

    return chk
  }
  
  enable(name) {
    let chk = this.find(name)
    if (!chk) return false
  
    chk.disabled = false

    this.writeChildState(chk)
  
    return chk
  }
  
  doAllChilds(op='kill', sig='SIGTERM') {
    let ch;
    for (let k in this.childs) {
      ch = this.childs[k];
      try {
        switch (op) {
          case 'kill':
            if (ch.state !== this.state.EXIT && ch.child && ch.child.kill)
              ch.child.kill(sig);
            break
  
          case 'stop':
          case 'remove':
          case 'safeRemove':
            ;(ch.state !== this.state.EXIT) && this[op](ch.name)
            break
  
        }
      } catch(err) {
          this.errorHandle(err, `--ERR-CHILD-${op.toUpperCase()}--`)
      }
    }//end for
  }
  
  remove(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    if (chk.state === this.state.RUNNING) {
        chk.restart = 'remove'
        try {
          this.killAllChilds(chk.child.pid, 'SIGKILL')
          ;(chk.state === this.state.RUNNING) && chk.child.kill('SIGKILL')
        } catch (err){}

        chk.state = this.state.EXIT
    }
  
    let ck = this.appName[name]
    
    let ind = this.monitorChilds.indexOf(ck)
    
    ind >= 0 && this.monitorChilds.splice(ind, 1)
  
    delete this.appName[name]
    delete this.childs[ck]
  
    fs.unlink(this.appStateFile(chk), err => {
      this.debug && err && console.error(err)
    })

    return chk
  }
  
  safeRemove(name, timeout=1000, callback=null) {
    if (typeof timeout === 'function') {
      callback = timeout
      timeout = 1000
    }

    let chk = this.find(name)
  
    if (!chk) return false
  
    this.stop(name, timeout)
  
    setTimeout(() => {
      this.remove(name)
      try {
        callback && (typeof callback === 'function') && callback(chk)
      } catch (err) {
        this.debug && this.errorHandle(err, '--ERR-SAFE-REMOVE-CALLBACK--')
      }
    }, _realTimeout(timeout, chk.stopTimeout))
    
    return chk
  }
  
  add(cfg) {
    if (!this.checkConfig(cfg)) return false
  
    return this.tryMakeChild(cfg)
  }
  
  has(name) {
    if (this.appName[name] && this.childs[ this.appName[name] ]) {
      return true
    }
    
    return false
  }
  
  stop(name, timeout=1000, callback=null) {
    let chk = this.find(name)

    if (!chk) return false
  
    if (!chk.child || chk.child.exitCode !== null) {
      chk.restart = 'none'
      chk.state = this.state.EXIT
      this.writeChildState(chk)

      if (callback && typeof callback === 'function') {
        return callback(chk)
      }

      return true
    }
  
    if (typeof timeout === 'function') {
      callback = timeout
      timeout = 5000
    }
  
    let pid = chk.child.pid
  
    chk.restart = 'none'
  
    try {
      if (chk.state === this.state.PAUSE) {
        process.kill(chk.child.pid, 'SIGCONT')
      }
      this.killAllChilds(chk.child.pid, 'SIGTERM')
      process.kill(chk.child.pid, 'SIGTERM')
    } catch (err) {
      this.errorHandle(err, '--ERR-STOP--')
    }
  
    let callback_run = false
  
    queueMicrotask(() => {
      if (chk.state === this.state.EXIT) {
        this.writeChildState(chk)
        if (callback && (typeof callback === 'function')) {
          callback_run = true
          callback(chk)
        }
      }
    })
  
    let check_callback = () => {
        chk.stopTimer = null
        try {
          if (chk.child && chk.child.exitCode === null && chk.child.pid === pid) {
            this.killAllChilds(pid, 'SIGKILL')
            process.kill(pid, 'SIGKILL')
          }
        } catch (err) {
          this.errorHandle(err, '--ERR-STOP-KILL--')
        }

        try {
          chk.state = this.state.EXIT
          this.writeChildState(chk)
          !callback_run && callback && (typeof callback === 'function') && callback(chk)
        } catch (err) {
          this.errorHandle(err, '--ERR-STOP-CALLBACK--')
        }
    }
  
    chk.stopTimer = setTimeout(check_callback, _realTimeout(timeout, chk.stopTimeout))

    return chk
  }
  
  start(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    chk.restart = chk.restartCache
  
    //若还没有退出则清理定时器即可。
    chk.stopTimer && clearTimeout(chk.stopTimer)
    chk.stopTimer = null
  
    if (chk.state === this.state.RUNNING) return chk

    if (chk.state === this.state.PAUSE) {
      return this.resume(name)
    }
  
    return this.startChild(chk)
  }
  
  restart(name, timeout=2000) {
    let chk = this.find(name)

    if (!chk) {
      return false
    }
  
    if (chk.restarting) {
      return true
    }

    chk.restarting = true
  
    return this.stop(name, timeout, (c) => {
      this.start(name)
    })
  }
  
  resetCount(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    chk.restartCount = 0
  
    return chk
  }
  
  restartCount(name, timeout = 2000) {
    if (!this.resetCount(name)) return false
  
    this.restart(name, timeout)
  }
  
  pause(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    if (chk.state === this.state.RUNNING) {
      try {
        process.kill(chk.child.pid, 'SIGSTOP')
        chk.state = this.state.PAUSE
        this.writeChildState(chk)
      } catch (err) {
        console.error(err)
      }
    }
    
    return chk
  }
  
  resume(name) {
    let chk = this.find(name)
  
    if (!chk) return false
  
    if (chk.state === this.state.PAUSE && chk.child) {
      try {
        process.kill(chk.child.pid, 'SIGCONT')
        chk.state = this.state.RUNNING
        this.writeChildState(chk)
      } catch (err) {
        this.errorHandle(err, '--ERR-RESUME--')
      }
    }

    return chk
  }
  
  resetNetData(name) {
    let chk = this.find(name)
    if (!chk) return false
  
    if (!chk.loadinfo.net) return true
  
    let keys = ['recvBytes', 'recvPackets', 'transmitBytes', 'transmitPackets']
  
    for (let k of keys) {
      chk.loadinfo.net[k] = 0
    }
  
    chk.loadinfo.net.devData = {}
  }
  
  async _getOneProcInfo(ch, kname) {
    if (ch && ch.child && ch.child.pid
      && ch.child.exitCode === null
      && ch.state === this.state.EXIT)
    {
      ch.state = this.state.RUNNING
    }

    if (ch && ch.state !== this.state.EXIT && ch.child) {
      parse_linux_proc_stat(ch.child.pid, ch.loadinfo[kname])
              .then(proc => {
                if (!proc) return falsech.loadinfo[kname]
                proc.rssKB = proc.rss * this._memPagesize

                if (!ch.limit || ch.limit.maxrss <= 0) return true
  
                if (proc.rssKB > (ch.limit.maxrss + ch.limit.rssOffset)) {
                  if (!ch.limit.maxRestart
                      || (ch.limit.maxRestart > 0 && ch.limit.rssRestartCount < ch.limit.maxRestart)
                  ) {
                    if (!ch.restarting) {
                      ch.limit.rssRestartCount++
                      ch.cause = 'maxrss|restart|超过最大内存限制进行重启'
                      this.restart(ch.name)
                    }
                  } else {
                    ch.limit.rssRestartCount = 0
                    ch.cause = 'maxrss|stop|超过最大内存限制退出'
                    this.stop(ch.name)
                  }
                }
              })
              .catch(err => {
                this.errorHandle(err, '--ERR-PROC-STAT--')
              })
      
        if (ch.monitorNetData) {
          if (ch.loadinfo.net) {
            parse_linux_pid_net(ch.child.pid, ch.loadinfo.net)
          } else {
            parse_linux_pid_net(ch.child.pid).then(netdata => {
              ch.loadinfo.net = netdata || null
            })
          }
        }
    }
  }
  
  async _getMonitorInfo(kname = 'cur') {
    for (let k of this.monitorChilds) {
      this._getOneProcInfo(this.childs[k], kname)
    }
  }
  
  _saveMonitorLast() {
    let ch
    for (let k of this.monitorChilds) {
        ch = this.childs[k]
        
        if (!ch) continue

        if (ch.state === this.state.EXIT || !ch.child) continue
  
        for (let k in ch.loadinfo.cur) {
          ch.loadinfo.last[k] = ch.loadinfo.cur[k]
        }
    }
  
  }
  
  _cacltChildsLoad() {
    let ch
    for (let k of this.monitorChilds) {
        ch = this.childs[k]

        //在进程信息切换期间有可能会出现空
        if (!ch) continue
        
        if (ch.state === this.state.EXIT || !ch.child) continue
  
        ch.loadinfo.cpu = fmt_percent( caclt_proc_cpu(ch.loadinfo.last,
                                              ch.loadinfo.cur,
                                              this._lastCPULoad[0], 
                                              this._curCPULoad[0]) );
  
        ch.loadinfo.mem = fmt_mem_value(ch.loadinfo.cur.rss * this._memPagesize)
    }
  }
  
  async _getLinuxSocket() {
    await parse_linux_socket(this.loadinfo.socket).catch(err => {
      this.errorHandle(err, '--ERR-CHILD-SOCKET--')
    })
  
    await parse_linux_socket6(this.loadinfo.socket6).catch(err => {
      this.errorHandle(err, '--ERR-CHILD-SOCKET6--')
    })
  }

  async monitorStart() {
    if (process.platform !== 'linux') {
      return false;
    }
  
    this._lastCPULoad = await parse_linux_cpu({all:true})
                                    .catch(err => {
                                      this.errorHandle(err, '--ERR-CPU-STAT--')
                                    });
  
    this.loadinfo.cpus = []
  
    for (let i = 0; i < this.cpuTotal; i++) {
      this.loadinfo.cpus.push(0)
      this.loadinfo.cpuns.push(0)
    }
  
    await parse_linux_mem().then(() => {
      (this.loadinfo.mem?.Hugetlb.value || this.loadinfo.mem?.HugePages_Total.value)
        && (this._memPagesize = this.loadinfo.mem.Hugepagesize.value);
  
    }).catch(err => {
      this.errorHandle(err, '--ERR-MEMINFO--')
    })
  
    await this._getMonitorInfo('last')
  
    await this._getLinuxSocket()
  
    this._cacltTotalPage()
    this._curProcList = this._nextProcLoad()
  
    //不频繁获取内存信息
    let meminfoCount = 0
  
    this.stepCount = this.maxStep
    let curMaxStep = this.maxStep
    let dyn_step_val = this.dynamicStep || 1
  
    if (typeof dyn_step_val !== 'number' || dyn_step_val < 1 || dyn_step_val >= this.dynamicMaxStep/2) {
      dyn_step_val = 1
    }
  
    this.monitorTimer = setInterval(async () => {
        this.stepCount++
        
        if (this.stepCount < curMaxStep) {
          return
        } else {
          this.stepCount = 0
        }
  
        if (this.dynamicMaxStep > this.maxStep) {
          curMaxStep += dyn_step_val
  
          if (curMaxStep >= this.dynamicMaxStep) {
            dyn_step_val = 0 - dyn_step_val
          } else if (curMaxStep <= this.maxStep) {
            dyn_step_val = 0 - dyn_step_val
          }
        }
        
        this._curCPULoad = await parse_linux_cpu({all: true})
                                    .catch(err => {
                                      this.errorHandle(err, '--ERR-CPU-STAT--')
                                    });
  
        await this._getMonitorInfo()
  
        this.loadinfo.cpun = caclt_cpu_load(this._lastCPULoad[0], this._curCPULoad[0])
        this.loadinfo.cpu = fmt_percent(this.loadinfo.cpun)
  
        for (let i = 0; i < this.cpuTotal; i += 1) {
          this.loadinfo.cpuns[i] = caclt_cpu_load(this._lastCPULoad[i+1], this._curCPULoad[i+1])
          this.loadinfo.cpus[i] = fmt_percent(this.loadinfo.cpuns[i])
        }
  
        this._cacltChildsLoad()
  
        this._getLinuxSocket()
  
        this._lastCPULoad = this._curCPULoad
        this._saveMonitorLast()
  
        meminfoCount += 1
  
        if (meminfoCount > 1) {
          await parse_linux_mem().catch(err => {
            this.errorHandle(err, '--ERR-MEMINFO--')
          })
          meminfoCount = 0
        }
  
        this.showLoadInfo()
  
    }, this.stepSlice)
  }
  
  monitorStop() {
    if (this.monitorTimer) {
      clearInterval(this.monitorTimer)
      this.monitorTimer = null
    }
  }
  
  showLoadInfo() {
    if (!this.loadInfoFile && process.ppid > 1) {
      console.clear()
    }
  
    let result = this.fmtLoadInfo(this.loadInfoType)
  
    if (this.loadInfoFile && this.loadInfoFile !== '--mem') {
      fs.writeFile(this.loadInfoFile,
          this.loadInfoType === 'json' ? JSON.stringify(result) : result, 
          err => {
              err && this.errorHandle(err, '--ERR-LOADINFO--')
          })
    } else if (!this.loadInfoFile) {
      console.log(result)
    }
  }
  
  _cacltTotalPage() {
    let total = this.monitorChilds.length
  
    this._totalPage = parseInt(total / this._procPagesize) + (total % this._procPagesize ? 1 : 0)
  
    return this._totalPage
  }
  
  _procLoadList() {
    let total = this.monitorChilds.length
  
    if (total === 0) return []
  
    let start = (this._procLoadCurPage - 1) * this._procPagesize
  
    return this.monitorChilds.slice(start, start + this._procPagesize)
  }
  
  _nextProcLoad() {
    this._procLoadCurPage++
    
    if (this._procLoadCurPage > this._totalPage) this._procLoadCurPage = 1
  
    return this._procLoadList()
  }
  
  _prevProcLoad() {
    this._procLoadCurPage--
    
    if (this._procLoadCurPage <= 0) this._procLoadCurPage = this._totalPage
  
    return this._procLoadList()
  }
  
  fmtLoadInfo(loadType='text') {
    let ch
  
    if (loadType === 'json') {
        let d = {
          pid: process.pid,
          sys: this.loadinfo,
          childs: []
        }
  
        for (let ck of this.monitorChilds) {
            ch = this.childs[ck];
  
            if (!ch) continue;

            d.childs.push({
                name: ch.name,
                state: this.stateName[ch.state],
                detail: ch.detail,
                command: ch.command,
                cause: ch.cause,
                only: ch.only,
                force: ch.force,
                disabled: ch.disabled,
                args: ch.args,
                cpu : ch.state === this.state.EXIT ? 0 : ch.loadinfo.cpu,
                mem: ch.state === this.state.EXIT ? 0 : ch.loadinfo.mem,
                pid: ch.state === this.state.EXIT ? -1 : ch.child.pid,
                net: ch.loadinfo.net,
                restart: ch.restart,
                restartCount: ch.restartCount,
                restartLimit: ch.restartLimit,
                restartDelay: ch.restartDelay,
                limit: ch.limit,
            });

        }
  
        return d;
  
    } else if (loadType === 'text') {
        //'\x1bcRIS\x1b[5;10r'
        let loadtext = '\x1bcRIS\x1b[r'
  
        let li = this.loadinfo
  
        let memused = li.mem.MemTotal.value - li.mem.MemAvailable.value
  
        let memused_percent = memused / li.mem.MemTotal.value
  
        if (this.showColor) {
            if (li.cpun >= 0.85 || memused_percent > 0.75) loadtext += '\x1b[1;35m';
            else if (li.cpun >= 0.6 || memused_percent > 0.65) loadtext += '\x1b[1;33m';
        }
    
        loadtext += ` CPU: ${li.cpu}% `
          + ` MEM: ${fmt_mem_value(memused)}`
          + `/${fmt_mem_value(li.mem.MemTotal.value)} MB  PID: ${process.pid}\n`
        
        this.showColor && (loadtext += '\x1b[0m');
  
        loadtext += ` Socket:  TCP ${li.socket.tcp.inuse}  UDP ${li.socket.udp.inuse}\n`
            + ` Socket6: TCP ${li.socket6.tcp.inuse}  UDP ${li.socket6.udp.inuse}\n`
    
        let count = 0
  
        li.cpuns.forEach((c, ind) => {
          if (this.showColor) {
            if (c >= 0.80) loadtext += '\x1b[1;35m';
            else if (c >= 0.50) loadtext += '\x1b[2;35m';
            else if (c >= 0.30) loadtext += '\x1b[1;33m';
            else loadtext += '\x1b[1;34m';
          }
  
          loadtext += ` CPU${count}: ${li.cpus[ind]}%          `.substring(0, 16)
          count += 1
  
          if (count % 4 === 0) {
            loadtext += '\n'
          }
  
          if (this.showColor) loadtext += '\x1b[0m';
        })
    
        loadtext += '\n'
  
        if (this._procLoopCount >= 5) {
          this._curProcList = this._nextProcLoad()
          this._procLoopCount = 0
        }
  
        this._procLoopCount += 1
    
        for (let k of this._curProcList) {
          ch = this.childs[k]
          
          if (!ch) continue;
      
          loadtext += ` Name: ${ch.name}                        `.substring(0, 32) + '\n'
  
          if (ch.state !== this.state.EXIT && ch.child) {
            loadtext += `   PID: ${ch.child.pid}                `.substring(0, 20)
            loadtext += `CPU: ${ch.loadinfo.cpu}%          `.substring(0, 16)
            loadtext += `MEM: ${ch.loadinfo.mem}MB\n`
            if (this.showColor) {
              ch.state === this.state.RUNNING && (loadtext += '\x1b[2;36m');
              ch.state === this.state.PAUSE && (loadtext += '\x1b[2;33m');
            }
          } else {
            this.showColor && (loadtext += '\x1b[2;37m')
          }
          
          loadtext += `   STATE: ${this.stateName[ch.state]}          `.substring(0, 20) 
          this.showColor && (loadtext += '\x1b[0m')
  
          loadtext += '\n\n'
      
        }
  
        let row = this._procPagesize * 4 + 6
  
        loadtext += `\x1b[${row};0H  \x1b[7m              ${this._procLoadCurPage} · ${this._totalPage}`
                    +`          Total: ${this.monitorChilds.length}        \x1b[0m`
  
        this._cacltTotalPage()
  
        return loadtext
    }
    
    return ''
  }
//end
}

module.exports = CDPC
